package com.itheima.pinda.strategy.impl;

import com.itheima.pinda.base.R;
import com.itheima.pinda.file.dto.chunk.FileChunksMergeDTO;
import com.itheima.pinda.file.entity.File;
import com.itheima.pinda.file.enumeration.IconType;
import com.itheima.pinda.properties.FileServerProperties;
import com.itheima.pinda.service.FileService;
import com.itheima.pinda.strategy.FileChunkStrategy;
import com.itheima.pinda.utils.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.IOException;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.locks.Lock;

/**
 * 文件分片处理 抽象策略类
 */
@Slf4j
public abstract class AbstractFileChunkStrategy implements FileChunkStrategy {
    @Autowired
    protected FileService fileService;
    @Autowired
    protected FileServerProperties fileProperties;

    protected FileServerProperties.Properties properties;

    @Override
    public R<File> chunksMerge(FileChunksMergeDTO info) {
        //定义文件合并后的名字
        String filename = new StringBuilder(info.getName()).append(StrPool.DOT).append(info.getExt()).toString();
        //分片合并
        R<File> result = chunksMerge(info, filename);
        if (result.getIsSuccess() && result.getData() != null) {
            File file = result.getData();
            LocalDateTime now = LocalDateTime.now();
            file.setDataType(FileDataTypeUtil.getDataType(info.getContextType()))
                    .setCreateMonth(DateUtils.formatAsYearMonthEn(now))
                    .setCreateWeek(DateUtils.formatAsYearWeekEn(now))
                    .setCreateDay(DateUtils.formatAsDateEn(now))
                    .setSubmittedFileName(info.getSubmittedFileName())
                    .setIsDelete(false)
                    .setSize(info.getSize())
                    .setFileMd5(info.getMd5())
                    .setContextType(info.getContextType())
                    .setFilename(filename)
                    .setExt(info.getExt())
                    .setIcon(IconType.getIcon(info.getExt()).getIcon());

            fileService.save(file);
            return R.success(file);
        }
        return result;
    }

    /**
     * 分片合并
     *
     * @param info
     * @param fileName
     * @return
     */
    protected R<File> chunksMerge(FileChunksMergeDTO info, String fileName) {
        //获取配置文件的存储位置
        String storagePath = fileProperties.getStoragePath();
        String path = FileDataTypeUtil.getUploadPathPrefix(storagePath);
        //分片数量
        Integer chunks = info.getChunks();
        String md5 = info.getMd5();
        String folder = info.getName();
        int chunksNum = this.getChunksNum(Paths.get(path, folder).toString());
        if (chunksNum == chunks) {
            Lock lock = FileLock.getLock(folder);
            try {
                lock.lock();
                //获得所有的 分片文件
                List<java.io.File> files = new ArrayList<java.io.File>(Arrays.asList(this.getChunks(Paths.get(path, folder).toString())));
                //排序
                files.sort((f1, f2) -> NumberHelper.intValueOf0(f1.getName()) - NumberHelper.intValueOf0(f2.getName()));
                R<File> result = merge(files, fileName, info);
                //清理：文件夹，tmp文件
                this.cleanSpace(folder, path);
                return result;

            } catch (Exception e) {
                log.error("数据分片合并失败", e);
                return R.fail("数据分片合并失败");
            } finally {
                lock.unlock();
                FileLock.removeLock(folder);
            }
        }
        log.error("文件[签名:" + md5 + "]数据不完整，可能该文件正在合并中");
        return R.fail("数据不完整，可能该文件正在合并中, 也有可能是上传过程中某些分片丢失");
    }

    protected abstract R<File> merge(List<java.io.File> files, String fileName, FileChunksMergeDTO info) throws Exception;

    protected boolean cleanSpace(String folder, String path) {
        java.io.File garbage = new java.io.File(Paths.get(path, folder).toString());
        if (!FileUtils.deleteQuietly(garbage)) {
            return false;
        }
        garbage = new java.io.File(Paths.get(path, folder + ".tmp").toString());
        if (!FileUtils.deleteQuietly(garbage)) {
            return false;
        }
        return true;
    }

    private java.io.File[] getChunks(String folder) {
        java.io.File file = new java.io.File(folder);
        java.io.File[] files = file.listFiles(item -> !item.isDirectory());
        return files;
    }


    protected int getChunksNum(String folder) {
        java.io.File file = new java.io.File(folder);
        java.io.File[] files = file.listFiles(item -> !item.isDirectory());
        return files.length;
    }
}